<?php

namespace app\command\workerman\process\timing;

use Workerman\Timer;

class Client
{
	private int|null $timer = null;

	private bool $run = false;

	/**
	 * 指定时间
	 * 是否在运行时执行一次
	 * @var bool
	 */
	private bool $before = false;

	private int $nextTime = 0;

	/**
	 * 跳动间隔s
	 * @var int
	 */
	private int $interval = 1;

	/**
	 * 执行时间
	 * [周1~7，月1~12，日1~31，小时00~23，分钟00~59，秒00~59]
	 * 时分秒位置可省略，自动填充00
	 * @var array
	 */
	private array $fixTime = [
		'week'   => null,
		'month'  => null,
		'day'    => null,
		'hour'   => '00',
		'minute' => '00',
		'second' => '00'
	];
	private bool $isFixTime = false;

	/**
	 * 不执行的时间
	 * [周1~7，月1~23，日1~31，小时00~23，分钟00~59，秒00~59]
	 * 从左到右依次命中
	 * 支持设置多个排除
	 * @var array
	 */
	private array $exclude = [
		'week'   => null,
		'month'  => null,
		'day'    => null,
		'hour'   => null,
		'minute' => null,
		'second' => null
	];
	private bool $isExclude = false;

	private string $class;

	private string $job;

	public function setBefore($before = true): static
	{
		$this->before = $before;
		return $this;
	}

	public function setInterval(int $interval = 1): static
	{
		$this->interval = $interval;
		return $this;
	}

	/**
	 * 固定时间执行
	 *
	 * 每周几：(1, null, null, 06, 06, 06)
	 * 每月几号：(null, null, 6, 06, 06, 06)
	 * 每天： (null, null, null, 06, 06, 06)
	 * 6月6日6时6分6秒：(null, 6, 6, 06, 06, 06)
	 * @param int|string|null $week
	 * @param int|string|null $month
	 * @param int|string|null $day
	 * @param string|null     $hour
	 * @param string|null     $minute
	 * @param string|null     $second
	 * @return $this
	 */
	public function setFixTime(
		int|string|null $week = null,
		int|string|null $month = null,
		int|string|null $day = null,
		string|null     $hour = null,
		string|null     $minute = null,
		string|null     $second = null
	): static
	{
		$this->isFixTime = true;

		// 设置指定key
		if (is_string($week) && !is_numeric($week)) {
			$this->fixTime[$week] = $month;
			return $this;
		}

		$this->fixTime['week']   = $week;
		$this->fixTime['month']  = $month;
		$this->fixTime['day']    = $day;
		$this->fixTime['hour']   = $hour;
		$this->fixTime['minute'] = $minute;
		$this->fixTime['second'] = $second;
		return $this;
	}

	public function setFixTimeWeek(int|string|null $val): static
	{
		return $this->setFixTime('week', $val);
	}

	public function setFixTimeMonth(int|string|null $val): static
	{
		return $this->setFixTime('month', $val);
	}

	public function setFixTimeDay(int|string|null $val): static
	{
		return $this->setFixTime('day', $val);
	}

	public function setFixTimeHour(int|string|null $val): static
	{
		return $this->setFixTime('hour', $val);
	}

	public function setFixTimeMinute(int|string|null $val): static
	{
		return $this->setFixTime('minute', $val);
	}

	public function setFixTimeSecond(int|string|null $val): static
	{
		return $this->setFixTime('second', $val);
	}

	/**
	 * 支持多个排除点
	 * @param int|string|array|null $week
	 * @param int|string|array|null $month
	 * @param int|string|array|null $day
	 * @param string|array|null     $hour
	 * @param string|array|null     $minute
	 * @param string|array|null     $second
	 * @return $this
	 */
	public function setExclude(
		int|string|array|null $week = null,
		int|string|array|null $month = null,
		int|string|array|null $day = null,
		string|array|null     $hour = null,
		string|array|null     $minute = null,
		string|array|null     $second = null
	): static
	{
		$this->isExclude = true;

		// 设置指定key
		if (is_string($week) && !is_numeric($week)) {
			$this->exclude[$week] = !is_array($month) ? [$month] : $month;
			return $this;
		}

		$this->exclude['week']   = $week;
		$this->exclude['month']  = $month;
		$this->exclude['day']    = $day;
		$this->exclude['hour']   = $hour;
		$this->exclude['minute'] = $minute;
		$this->exclude['second'] = $second;
		return $this;
	}

	public function setExcludeWeek(int|string|array|null $val = null): static
	{
		return $this->setExclude('week', $val);
	}

	public function setExcludeMonth(int|string|array|null $val = null): static
	{
		return $this->setExclude('month', $val);
	}

	public function setExcludeDay(int|string|array|null $val = null): static
	{
		return $this->setExclude('day', $val);
	}

	public function setExcludeHour(string|array|null $val = null): static
	{
		return $this->setExclude('hour', $val);
	}

	public function setExcludeMinute(string|array|null $val = null): static
	{
		return $this->setExclude('minute', $val);
	}

	public function setExcludeSecond(string|array|null $val = null): static
	{
		return $this->setExclude('second', $val);
	}

	public function run($class, $job = 'job'): void
	{
		$this->class = $class;
		$this->job   = $job;

		$this->work();
	}

	private function work(): void
	{
		if ($this->run) return;

		$this->run = true;

		if ($this->isFixTime) {
			// 指定时间后只能步长1秒
			$this->interval = 1;
		}

		try {

			// 运行时执行一次
			if ($this->before) {
				app($this->class)->{$this->job}();
				$this->before = false;
			}

			$this->timer = Timer::add($this->interval, function () {

				if (time() < $this->nextTime) return;

				$checkStatus = true;

				if (!$this->isFixTime) {

					// 间隔
					$nextTime = $this->interval - 1;

				} else {

					$nextTime = 60;

					// 固定日期
					if (is_null($this->fixTime['week']) && !is_null($this->fixTime['month']) && !is_null($this->fixTime['day'])) {
						if (($this->fixTime['month'] . $this->fixTime['day']) != date('nj')) return;
					}

					// 每周几
					if (!is_null($this->fixTime['week']) && is_null($this->fixTime['month']) && is_null($this->fixTime['day'])) {
						if (date('N') != $this->fixTime['week']) return;
					}

					// 每月几号
					if (is_null($this->fixTime['week']) && is_null($this->fixTime['month']) && !is_null($this->fixTime['day'])) {
						if (date('j') != $this->fixTime['day']) return;
					}

					// 检查时分秒
					if (($this->fixTime['hour'] . $this->fixTime['minute'] . $this->fixTime['second']) != date('His')) {
						$checkStatus = false;
					}

				}

				// 排除
				if ($this->isExclude) {
					if (!is_null($this->exclude['week'])) { // 周
						if (in_array(date('N'), $this->exclude['week'])) return;
					}
					if (!is_null($this->exclude['month'])) { // 月
						if (in_array(date('n'), $this->exclude['month'])) return;
					}
					if (!is_null($this->exclude['day'])) { // 日
						if (in_array(date('j'), $this->exclude['day'])) return;
					}
					if (!is_null($this->exclude['hour'])) { // 时
						if (in_array(date('H'), $this->exclude['hour'])) return;
					}
					if (!is_null($this->exclude['minute'])) { // 分
						if (in_array(date('i'), $this->exclude['minute'])) return;
					}
					if (!is_null($this->exclude['second'])) { // 秒
						if (in_array(date('s'), $this->exclude['second'])) return;
					}
				}

				if ($checkStatus) {
					$this->nextTime = time() + $nextTime;
					app($this->class)->{$this->job}();
				}


			});

		} catch (\Throwable|\Exception|\Error $e) {

			if (!is_null($this->timer)) {
				Timer::del($this->timer);
			}

			$error = [
				'message' => $e->getMessage(),
				'line'    => $e->getLine(),
				'trace'   => $e->getTraceAsString()
			];

			//dump($error);

			systemLogRecord('Command Timing', $error, 'error');
			unset($error);

		}
	}
}